package credit.ccx.com.walletcore.util;

import android.util.Log;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;

import org.bitcoinj.core.ECKey;
import org.bitcoinj.crypto.ChildNumber;
import org.bitcoinj.params.MainNetParams;
import org.bitcoinj.wallet.DeterministicKeyChain;
import org.bitcoinj.wallet.DeterministicSeed;
import org.web3j.crypto.Bip39Wallet;
import org.web3j.crypto.CipherException;
import org.web3j.crypto.Credentials;
import org.web3j.crypto.ECKeyPair;
import org.web3j.crypto.Keys;
import org.web3j.crypto.Wallet;
import org.web3j.crypto.WalletFile;
import org.web3j.crypto.WalletUtils;
import org.web3j.utils.Numeric;

import java.io.File;
import java.io.IOException;
import java.math.BigInteger;
import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;

import javax.annotation.Nonnull;

import credit.ccx.com.walletcore.base.BaseApplication;
import credit.ccx.com.walletcore.model.WalletModel;

import static org.web3j.crypto.Hash.sha256;
import static org.web3j.crypto.Keys.ADDRESS_LENGTH_IN_HEX;
import static org.web3j.crypto.Keys.PRIVATE_KEY_LENGTH_IN_HEX;

/**
 * Created by gongdongyang on 2018/7/4.
 */

public class WalletExtUtils {


    private static final String TAG = "WalletExtUtils";
    private static final ObjectMapper objectMapper = new ObjectMapper();
    private static final SecureRandom secureRandom = SecureRandomUtils.secureRandom();

    static {
        objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    }

    public static String generateFullNewWalletFile(String password, File destinationDirectory)
            throws NoSuchAlgorithmException, NoSuchProviderException,
            InvalidAlgorithmParameterException, CipherException, IOException {

        return generateNewWalletFile(password, destinationDirectory, true);
    }

    public static String generateLightNewWalletFile(String password, File destinationDirectory)
            throws NoSuchAlgorithmException, NoSuchProviderException,
            InvalidAlgorithmParameterException, CipherException, IOException {

        return generateNewWalletFile(password, destinationDirectory, false);
    }

    public static String generateNewWalletFile(
            String password, File destinationDirectory, boolean useFullScrypt)
            throws CipherException, IOException, InvalidAlgorithmParameterException,
            NoSuchAlgorithmException, NoSuchProviderException {

        ECKeyPair ecKeyPair = Keys.createEcKeyPair();
        return generateWalletFile(password, ecKeyPair, destinationDirectory, useFullScrypt);
    }

    public static String generateWalletFile(
            String password, ECKeyPair ecKeyPair, File destinationDirectory, boolean useFullScrypt)
            throws CipherException, IOException {

        WalletFile walletFile;
        if (useFullScrypt) {
            walletFile = Wallet.createStandard(password, ecKeyPair);
        } else {
            walletFile = Wallet.createLight(password, ecKeyPair);
        }

        String fileName = getWalletFileName(walletFile);
        File destination = new File(destinationDirectory, fileName);

        objectMapper.writeValue(destination, walletFile);

        return fileName;
    }

    /**
     * Generates a BIP-39 compatible Ethereum wallet. The private key for the wallet can
     * be calculated using following algorithm:
     * <pre>
     *     Key = SHA-256(BIP_39_SEED(mnemonic, password))
     * </pre>
     *
     * @param password Will be used for both wallet encryption and passphrase for BIP-39 seed
     * @param destinationDirectory The directory containing the wallet
     * @return A BIP-39 compatible Ethereum wallet
     * @throws CipherException if the underlying cipher is not available
     * @throws IOException if the destination cannot be written to
     * *产生一个bip-39兼容伊斯利恩钱包。钱包的私钥可以

     *使用下面的算法计算：

     * <前>

     *关键= SHA-256（bip_39_seed（记忆密码））

     * <前>

     *

     * @param密码将用于钱包加密和密码bip-39种子

     * @param

     * @返回bip-39兼容伊斯利恩钱包

     * @投cipherexception如果基础密码不可用

     * @抛出IOException如果目标无法写入
     */
    public static WalletModel generateBip39WalletETH(String password, String mnemonic, File destinationDirectory)
            throws CipherException, IOException {
        /*byte[] initialEntropy = new byte[16];
        secureRandom.nextBytes(initialEntropy);

        //读记助词
        String mnemonic = MyMnemonicUtils.generateMnemonic(initialEntropy);*/
        byte[] seed = MyMnemonicUtils.generateSeed(mnemonic, "");
        String walletFile = null;
        try {
            DeterministicSeed deterministicSeed =  new  DeterministicSeed(mnemonic,seed,"",0);
            DeterministicKeyChain deterministicKeyChain = DeterministicKeyChain.builder().seed(deterministicSeed).build();
            BigInteger key = deterministicKeyChain.getKeyByPath(parsePath("M/44'/60'/0'/0/0"),true).getPrivKey();
            ECKeyPair privateKey = ECKeyPair.create(key);
            walletFile = generateWalletFile(password, privateKey, destinationDirectory, false);
        } catch (Exception e) {
            e.printStackTrace();
        }
        Bip39Wallet bip39Wallet = new Bip39Wallet(walletFile, mnemonic);
        String path = destinationDirectory.getAbsoluteFile() + "/" + bip39Wallet.getFilename();
        SPUtils.put(BaseApplication.application,"P_ETH",path);
        Credentials credentials = WalletUtils.loadCredentials("", path);
        WalletModel zWallet = new WalletModel(credentials.getAddress(),credentials.getEcKeyPair().getPrivateKey().toString(),
                credentials.getEcKeyPair().getPublicKey().toString(),
                bip39Wallet.getMnemonic());
        return zWallet;
    }

    public static WalletModel generateBip39WalletBTC(String password, String mnemonic, File destinationDirectory)
             {
        byte[] seed = MyMnemonicUtils.generateSeed(mnemonic, "");
        //String walletFile = null;
        WalletModel zWallet = null;
        try {
            DeterministicSeed deterministicSeed =  new  DeterministicSeed(mnemonic,seed,"",0);
            DeterministicKeyChain deterministicKeyChain = DeterministicKeyChain.builder().seed(deterministicSeed).build();
            BigInteger privKeyBTC = deterministicKeyChain.getKeyByPath(parsePath("M/44'/0'/0'/0/"),true).getPrivKey();
            ECKey ecKey = ECKey.fromPrivate(privKeyBTC);
            String publickey = Numeric.toHexStringNoPrefixZeroPadded(new BigInteger(ecKey.getPubKey()), 66);
            //正式环境应该是主网参数
            String privateKey = ecKey.getPrivateKeyEncoded(MainNetParams.get()).toString();

            //地址
            String address = ecKey.toAddress(MainNetParams.get()).toString();
            Log.d("ChannelIndex","address=="+address);
            Log.d("ChannelIndex","publickey=="+publickey);
            Log.d("ChannelIndex","privateKey=="+privateKey);
            zWallet = new WalletModel(address,privateKey,publickey,mnemonic);


        } catch (Exception e) {
            e.printStackTrace();
        }
        //生成种子密钥
        return zWallet;
    }


    public static Credentials loadCredentials(String password, String source)
            throws IOException, CipherException {
        return loadCredentials(password, new File(source));
    }

    public static Credentials loadCredentials(String password, File source)
            throws IOException, CipherException {
        WalletFile walletFile = objectMapper.readValue(source, WalletFile.class);
        return Credentials.create(Wallet.decrypt(password, walletFile));
    }

    public static Credentials loadBip39Credentials(String password, String mnemonic) {
        byte[] seed = MyMnemonicUtils.generateSeed(mnemonic, password);
        return Credentials.create(ECKeyPair.create(sha256(seed)));
    }

    private static String getWalletFileName(WalletFile walletFile) {
//        DateTimeFormatter format = DateTimeFormatter.ofPattern(
//                "'UTC--'yyyy-MM-dd'T'HH-mm-ss.nVV'--'");
//        ZonedDateTime now = ZonedDateTime.now(ZoneOffset.UTC);

//        return now.format(format) + walletFile.getAddress() + ".json";
        return  walletFile.getAddress() + ".json";
    }

    public static String getDefaultKeyDirectory() {
        return getDefaultKeyDirectory(System.getProperty("os.name"));
    }

    static String getDefaultKeyDirectory(String osName1) {
        String osName = osName1.toLowerCase();

        if (osName.startsWith("mac")) {
            return String.format(
                    "%s%sLibrary%sEthereum", System.getProperty("user.home"), File.separator,
                    File.separator);
        } else if (osName.startsWith("win")) {
            return String.format("%s%sEthereum", System.getenv("APPDATA"), File.separator);
        } else {
            return String.format("%s%s.ethereum", System.getProperty("user.home"), File.separator);
        }
    }

    public static String getTestnetKeyDirectory() {
        return String.format(
                "%s%stestnet%skeystore", getDefaultKeyDirectory(), File.separator, File.separator);
    }

    public static String getMainnetKeyDirectory() {
        return String.format("%s%skeystore", getDefaultKeyDirectory(), File.separator);
    }

    public static boolean isValidPrivateKey(String privateKey) {
        String cleanPrivateKey = Numeric.cleanHexPrefix(privateKey);
        return cleanPrivateKey.length() == PRIVATE_KEY_LENGTH_IN_HEX;
    }

    public static boolean isValidAddress(String input) {
        String cleanInput = Numeric.cleanHexPrefix(input);

        try {
            Numeric.toBigIntNoPrefix(cleanInput);
        } catch (NumberFormatException e) {
            return false;
        }

        return cleanInput.length() == ADDRESS_LENGTH_IN_HEX;
    }

    /**
     * The path is a human-friendly representation of the deterministic path. For example:
     *
     * "44H / 0H / 0H / 1 / 1"
     *
     * Where a letter "H" means hardened key. Spaces are ignored.
     * M/44'/60'/0'/0/0
     */
    public static List<ChildNumber> parsePath(@Nonnull String path) {
        String[] parsedNodes = path.replace("M", "").split("/");
        List<ChildNumber> nodes = new ArrayList<ChildNumber>();

        for (String n : parsedNodes) {
            n = n.replaceAll(" ", "");
            if (n.length() == 0) continue;
            boolean isHard = n.endsWith("'");
            if (isHard) n = n.substring(0, n.length() - 1);
            int nodeNumber = Integer.parseInt(n);
            nodes.add(new ChildNumber(nodeNumber, isHard));
        }

        return nodes;
    }
}
